package lxs.swift.operation;

import lxs.swift.collector.sync.Mutex;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class Flush<V> {
    private final Supplier<V> get;
    private final Consumer<V> set;

    private Flush(Supplier<V> get, Consumer<V> set) {
        Objects.requireNonNull(get);
        Objects.requireNonNull(set);
        this.get = get;
        this.set = set;
    }

    /**
     * 用新值写入对象，并返回旧值
     *
     * @return 旧值
     */
    public V swapOld(@NotNull Function<V, V> func) {
        V v = get.get();
        V nv = func.apply(v);
        set.accept(nv);
        return v;
    }

    /**
     * 用新值写入对象，并返回新值
     *
     * @return 新值
     */
    public V swapNew(@NotNull Function<V, V> func) {
        V v = get.get();
        V nv = func.apply(v);
        set.accept(nv);
        return nv;
    }

    /**
     * 返回线程安全的Flush对象
     */
    public Flush<V> sync() {
        Flush<V> vFlush = this;
        return new Flush<V>(this.get, this.set) {
            final Mutex<Flush<V>> mutex = new Mutex<>(vFlush);

            @Override
            public V swapOld(@NotNull Function<V, V> func) {
                return mutex.map(v -> v.swapOld(func));
            }

            @Override
            public V swapNew(@NotNull Function<V, V> func) {
                return mutex.map(v -> v.swapNew(func));
            }
        };
    }

    public static <V> Flush<V> of(@NotNull Supplier<V> get, @NotNull Consumer<V> set) {
        return new Flush<>(get, set);
    }

    public static StrFlush ofStr(@NotNull Supplier<String> get, @NotNull Consumer<String> set) {
        return new StrFlush(get, set);
    }

    public static IntFlush ofInt(@NotNull Supplier<Integer> get, @NotNull Consumer<Integer> set) {
        return new IntFlush(get, set);
    }

    public static LongFlush ofLong(@NotNull Supplier<Long> get, @NotNull Consumer<Long> set) {
        return new LongFlush(get, set);
    }

    public static DoubleFlush ofDouble(@NotNull Supplier<Double> get, @NotNull Consumer<Double> set) {
        return new DoubleFlush(get, set);
    }

    public static class StrFlush extends Flush<String> {
        private StrFlush(Supplier<String> get, Consumer<String> set) {
            super(get, set);
        }

        public void append(@NotNull String str) {
            this.swapNew(v -> v + str);
        }

        public void replace(@NotNull String target, @NotNull String replacement) {
            this.swapNew(v -> v.replace(target, replacement));
        }
    }

    public static class IntFlush extends Flush<Integer> {
        private IntFlush(Supplier<Integer> get, Consumer<Integer> set) {
            super(get, set);
        }

        public void acc(int n) {
            this.swapNew(v -> v + n);
        }

        public void mul(int n) {
            this.swapNew(v -> v * n);
        }

        public void div(int n) {
            this.swapNew(v -> v / n);
        }

        public void mod(int n) {
            this.swapNew(v -> v % n);
        }
    }

    public static class LongFlush extends Flush<Long> {
        private LongFlush(Supplier<Long> get, Consumer<Long> set) {
            super(get, set);
        }

        public void acc(long n) {
            this.swapNew(v -> v + n);
        }

        public void mul(long n) {
            this.swapNew(v -> v * n);
        }

        public void div(long n) {
            this.swapNew(v -> v / n);
        }

        public void mod(long n) {
            this.swapNew(v -> v % n);
        }
    }

    public static class DoubleFlush extends Flush<Double> {
        private DoubleFlush(Supplier<Double> get, Consumer<Double> set) {
            super(get, set);
        }

        public void acc(double n) {
            this.swapNew(v -> v + n);
        }

        public void mul(double n) {
            this.swapNew(v -> v * n);
        }

        public void div(double n) {
            this.swapNew(v -> v / n);
        }

        public void mod(double n) {
            this.swapNew(v -> v % n);
        }
    }
}
